<script lang="ts" module>
    import { untrack, type Snippet } from "svelte";
    import type { NodeKeyType, TreeNode, TreeProps } from ".";
    import { TreeCheckMod } from "./Tree.svelte";

    export interface TreeStoreProps {
        data: TreeNode[];
        nodeMap: Record<NodeKeyType, TreeNode>;
        nodeList: TreeNode[];
    }

    export class TreeStore {
        store: TreeStoreProps;
        data: TreeNode[] = [];
        flatData: TreeNode[] = [];
        keyField = "id";
        titleField = "title";
        checkable: boolean;
        draggable: boolean;
        checkRelation: string;
        mode: TreeCheckMod;
        props: any;
        expandedKeys: NodeKeyType[] = $state([]);
        selectedKey: NodeKeyType | undefined = $state();

        constructor(store: TreeStoreProps, props: any) {
            this.store = store;
            this.props = props;
            this.checkable = props.checkable ?? false;
            this.checkRelation = props.checkRelation ?? "related";
            this.draggable = props.draggable ?? false;
            this.keyField = props.keyField || "id";
            this.titleField = props.titleField || "title";
            this.mode = props.mode ?? TreeCheckMod.HALF;
        }

        _buildRelation = (data: TreeNode[], parent: any, level: number, map: Record<NodeKeyType, TreeNode>) => {
            data.forEach((item: TreeNode) => {
                map[item[this.keyField]] = item;
                item._parent = parent;
                item._level = level;
                item.visible = true;
                if (item.children) {
                    this._buildRelation(item.children, item, level + 1, map);
                }
            });
        };

        /**
         * 构建父子关系和层级关系
         * @param data
         * @param parent
         * @param level
         */
        buildRelation = (data: TreeNode[], parent: any, level: number) => {
            const map: Record<NodeKeyType, TreeNode> = {};
            this._buildRelation(data, parent, level, map);
            return map;
        };

        setRootFlatNodes = () => {
            untrack(() => {
                this.store.nodeList = this.getFlatNodes(this.store.data);
            });
        };

        /**
         * 获取显示的树节点
         * @param nodes
         * @returns
         */
        getFlatNodes = (nodes: TreeNode[]): TreeNode[] => {
            const list: TreeNode[] = nodes.flatMap((item: TreeNode) => {
                if (this.expandedKeys.includes(item[(this, this.keyField)]) && item.children?.length && item.visible) {
                    return [item, this.getFlatNodes(item.children)].flat();
                } else {
                    return item.visible ? [item] : [];
                }
            });
            return list;
        };

        /**
         * 获取显示的树节点
         * @param nodes
         * @returns
         */
        getAllFlatNodes = (nodes: TreeNode[]): TreeNode[] => {
            const list: TreeNode[] = nodes.flatMap((item: TreeNode) => {
                if (item.children?.length) {
                    return [item, this.getAllFlatNodes(item.children)].flat();
                } else {
                    return [item];
                }
            });
            return list;
        };

        getStore() {
            return this.store;
        }

        openCloseNode = (node: TreeNode) => {
            const key = node[this.keyField];
            if (this.expandedKeys.includes(key)) {
                this.expandedKeys = this.expandedKeys.filter((item) => item !== key);
            } else {
                this.expandedKeys.push(key);
            }
        };

        /**
         * 选择节点
         * @param node
         * @param silence 不触发回调
         */
        selectNode = (nodeId: NodeKeyType | TreeNode, silence?: boolean) => {
            const node = this._getNode(nodeId);

            if (node) {
                this.selectedKey = node[this.keyField];
                untrack(() => {
                    this.onSelectedKeyChange(this.selectedKey!);
                });
                !silence && this.props.onnodeselect?.(node);
            }
        };

        onSelectedKeyChange = (selectedKey: NodeKeyType) => {};

        clearSelect = () => {
            this.selectedKey = "";
        };

        _storeNode = (node: TreeNode, map: Record<NodeKeyType, TreeNode>) => {
            map[node[this.keyField]] = node;
            node.visible = true;
            if (node.children && node.children?.length > 0) {
                node.children.forEach((item: any) => {
                    this._storeNode(item, map);
                });
            }
        };

        storeNode = (node: TreeNode) => {
            const map: Record<NodeKeyType, TreeNode> = {};
            this._storeNode(node, map);
            for (const key in map) {
                this.store.nodeMap[key] = map[key];
                this.flatData.push(map[key]);
            }
        };

        getNode = (key: NodeKeyType) => {
            return this.store.nodeMap[key];
        };

        _getNode = (nodeId: NodeKeyType | TreeNode) => {
            let node: TreeNode;
            if (typeof nodeId === "string" || typeof nodeId === "number") {
                node = this.store.nodeMap[nodeId];
            } else {
                node = nodeId as TreeNode;
                nodeId = node[this.keyField] as NodeKeyType;
            }
            return node;
        };

        removeNode = (node: TreeNode) => {
            if (node) {
                const parent: TreeNode | undefined = node._parent;
                if (parent) {
                    const index: number = parent.children!.findIndex((item: TreeNode) => item[this.keyField] === node[this.keyField]);
                    parent.children?.splice(index, 1);
                } else {
                    // 在根目录或是新节点
                    const index = this.store.data.findIndex((item: TreeNode) => item[this.keyField] === node[this.keyField]);
                    if (index > -1) {
                        this.store.data.splice(index, 1);
                    }
                }
            }
        };

        remove = (nodeId: NodeKeyType | TreeNode) => {
            const node = this._getNode(nodeId);
            this.removeNode(node);
        };

        beforeNodeOperation = (targetKey: any, nodeId: NodeKeyType | TreeNode) => {
            if (!this.store.nodeMap[targetKey]) {
                console.error("targetKey not exist");
                return false;
            }
            const node: TreeNode = this._getNode(nodeId);
            // 新节点需要将节点和子节点存入map
            if (!this.store.nodeMap[node[this.keyField]]) {
                this.storeNode(node);
            } else {
                // 删除节点
                this.removeNode(node);
            }

            return node;
        };

        append = (parentKey: any, nodeId: NodeKeyType | TreeNode) => {
            const node = this.beforeNodeOperation(parentKey, nodeId);
            if (!node) {
                return;
            }
            const target = this.store.nodeMap[parentKey];
            if (!target.children) {
                target.children = [];
            }
            const n = this.store.nodeMap[node[this.keyField]];
            this.updateLevels(n, target._level!);
            n._parent = target;
            target.children.push(n);
            if (this.checkable) {
                this.updateNodeCheckStatus(target);
            }
            this.setRootFlatNodes();
        };

        /**
         * 更新节点选择状态
         * @param nodeId
         */
        updateNodeCheckStatus = (nodeId: TreeNode | NodeKeyType | undefined) => {
            if (!nodeId) {
                return;
            }
            const node = this._getNode(nodeId);
            if (node) {
                node.checked = this.getNodeChecked(node);
                if (this.checkRelation === "related") {
                    this.setCheckedForwardUp(node);
                }
            }
        };

        updateLevels = (node: TreeNode, startLevel: number) => {
            node._level = startLevel + 1;
            if (node.children && node.children.length > 0) {
                node.children.forEach((item: any) => {
                    this.updateLevels(item, startLevel + 1);
                });
            }
        };

        prepend = (parentKey: any, nodeId: NodeKeyType | TreeNode) => {
            const node = this.beforeNodeOperation(parentKey, nodeId);
            if (!node) {
                return;
            }

            const target = this.store.nodeMap[parentKey];
            if (!target.children) {
                target.children = [];
            }
            const n = this.store.nodeMap[node[this.keyField]];
            this.updateLevels(n, target._level!);
            n._parent = target;
            target.children.unshift(n);
            // 更新状态
            if (this.checkable) {
                this.updateNodeCheckStatus(target);
            }
            this.setRootFlatNodes();
        };

        insertBefore = (targetKey: NodeKeyType, nodeId: NodeKeyType | TreeNode) => {
            const node = this.beforeNodeOperation(targetKey, nodeId);
            if (!node) {
                return;
            }

            const target = this.store.nodeMap[targetKey];
            const parent = target._parent || { children: this.store.data };
            const targetIndex = parent.children!.findIndex((item: TreeNode) => item[this.keyField] === targetKey);

            const n = this.store.nodeMap[node[this.keyField]];
            parent.children!.splice(targetIndex, 0, n);
            this.updateLevels(n, target._level! - 1);
            n._parent = target._parent;

            // 更新状态
            if (this.checkable) {
                this.updateNodeCheckStatus(target._parent);
            }
            this.setRootFlatNodes();
        };

        insertAfter = (targetKey: NodeKeyType, nodeId: NodeKeyType | TreeNode) => {
            const node = this.beforeNodeOperation(targetKey, nodeId);
            if (!node) {
                return;
            }

            const target = this.store.nodeMap[targetKey];
            const parent = target._parent || { children: this.store.data };
            const targetIndex = parent.children!.findIndex((item: TreeNode) => item[this.keyField] === targetKey);

            const n = this.store.nodeMap[node[this.keyField]];
            parent.children!.splice(targetIndex + 1, 0, n);
            this.updateLevels(n, target._level! - 1);
            n._parent = target._parent;

            // 更新状态
            if (this.checkable) {
                this.updateNodeCheckStatus(target._parent);
            }
            this.setRootFlatNodes();
        };

        filter = (keyword: string, filterMethod: any) => {
            const defaultFilterMethod = (keyword: string, node: TreeNode) => {
                const title = node[this.titleField];
                if (title == null || !title.toString) return false;
                return (title.toString() as string).toLowerCase().indexOf(keyword.toLowerCase()) > -1;
            };
            filterMethod = filterMethod || defaultFilterMethod;

            // 使用树形结构数据进行遍历
            const filterVisibleNodes: TreeNode[] = [];
            const visibleMap: Record<NodeKeyType, boolean> = {};
            this.flatData.forEach((node: TreeNode) => {
                visibleMap[node[this.keyField]] = (node._parent && visibleMap[node._parent[this.keyField]]) || filterMethod(keyword, node);

                this.store.nodeMap[node[this.keyField]].visible = visibleMap[node[this.keyField]];
                if (visibleMap[node[this.keyField]]) {
                    filterVisibleNodes.push(node);
                }
            });

            // 对于临时列表中的节点，都是可见的，因此将它们的父节点都设为可见并展开
            filterVisibleNodes.forEach((node) => {
                const stack = [];
                let parent = node._parent;
                while (parent) {
                    stack.unshift(parent);
                    parent = parent._parent;
                }
                stack.forEach((parent) => {
                    this.store.nodeMap[parent[this.keyField]]._filterVisible = true;
                    this.store.nodeMap[parent[this.keyField]].visible = (!parent._parent || parent._parent.visible) && parent._filterVisible;
                });
                node.visible = !node._parent || node._parent.visible;
            });

            this.setRootFlatNodes();
        };

        getNodeIndexInShow = (nodeId: TreeNode | NodeKeyType) => {
            const node = this._getNode(nodeId);
            if (!node || !node.visible) return -1;
            const index = this.store.nodeList.findIndex((n: TreeNode) => n[this.keyField] === node[this.keyField]);

            return index;
        };

        expandAll = () => {
            this.flatData.forEach((item: TreeNode) => {
                if (item.visible && item.children) {
                    const key = item[this.keyField];
                    if (!this.expandedKeys.includes(key)) {
                        this.expandedKeys.push(key);
                    }
                }
            });
            this.onChangeExpandedKeys(this.expandedKeys);
        };

        collapseAll = () => {
            this.expandedKeys = [];
            this.onChangeExpandedKeys(this.expandedKeys);
        };

        isExpand = (node: TreeNode) => {
            return this.expandedKeys.includes(node[this.keyField]);
        };

        checkNode = (nodeId: TreeNode | NodeKeyType, checked: boolean) => {
            const node = this._getNode(nodeId);
            node.checked = checked;
            if (this.checkRelation === "related") {
                this.setCheckedForwardDown(node, checked);
            }
            if (this.checkRelation === "related") {
                node.checked = this.getNodeChecked(node);
            }
            if (this.checkRelation === "related") {
                this.setCheckedForwardUp(node);
            }
            const checkedKeys = this.getCheckedKeys(this.mode);

            this.onValueChange(checkedKeys);
            this.props.onchange?.(checkedKeys);
        };

        onValueChange = (val: NodeKeyType[]) => {};

        setCheckedForwardDown = (node: TreeNode, checked: boolean) => {
            if (node.children) {
                node.children.forEach((item) => {
                    if (item.disabled) return;
                    item.checked = checked;
                    this.setCheckedForwardDown(item, checked);
                });
            }
        };

        getNodeChecked = (nodeId: TreeNode | NodeKeyType) => {
            const node = this._getNode(nodeId);
            if (!node.children || node.children.length === 0) {
                return node.checked;
            } else {
                let checked: boolean | "indeterminate" = false;
                let checkedNum = 0;
                let indeterminateNum = 0;
                node.children.forEach((item) => {
                    if (item.checked === true) {
                        checkedNum++;
                    }
                    if (item.checked === "indeterminate") {
                        indeterminateNum++;
                    }
                });
                if (checkedNum === node.children.length) {
                    checked = true;
                } else if (checkedNum > 0) {
                    checked = "indeterminate";
                }
                if (!checked && indeterminateNum > 0) {
                    checked = "indeterminate";
                }
                return checked;
            }
        };

        setCheckedForwardUp = (node: TreeNode) => {
            const parentNode = node._parent;
            if (parentNode) {
                const checked: boolean | "indeterminate" | undefined = this.getNodeChecked(parentNode);
                parentNode.checked = checked;

                this.setCheckedForwardUp(parentNode);
            }
        };

        rename = (nodeId: TreeNode | NodeKeyType, title: string) => {
            const node = this._getNode(nodeId);
            node[this.titleField] = title;
        };

        expandNode = (nodeId: TreeNode | NodeKeyType, expand: boolean) => {
            const node = this._getNode(nodeId);
            if (expand) {
                this.expandedKeys.push(node[this.keyField]);
                this.props.onnodeexpand?.(node);
            } else {
                this.expandedKeys = this.expandedKeys.filter((key) => key !== node[this.keyField]);
                this.props.onnodecollapse?.(node);
            }
            // console.log($state.snapshot(this.expandedKeys));
            this.onChangeExpandedKeys(this.expandedKeys);
        };

        onChangeExpandedKeys = (expandedKeys: NodeKeyType[]) => {};

        checkAll = () => {
            for (const key in this.store.nodeMap) {
                this.store.nodeMap[key].checked = true;
            }
            const checkedKeys = this.getCheckedKeys(this.mode);
            this.onValueChange(checkedKeys);
            this.props.onchange?.(checkedKeys);
        };

        uncheckAll = () => {
            for (const key in this.store.nodeMap) {
                this.store.nodeMap[key].checked = false;
            }
            const checkedKeys = this.getCheckedKeys(this.mode);
            this.onValueChange(checkedKeys);
            this.props.onchange?.(checkedKeys);
        };

        loadData = async (node: TreeNode, loadDataMethod: (node: TreeNode) => Promise<TreeNode[]>) => {
            node.__loading = true;
            try {
                const list = await loadDataMethod(node);
                if (list.length > 0) {
                    list.forEach((item) => {
                        this.append(node[this.keyField], item);
                    });
                }
            } catch (e) {
                //
            } finally {
                node.__loading = false;
            }
            node.loading = false;
        };

        /**
         *
         * @param mode
         * @returns
         */
        getChecked = (mode: TreeCheckMod = TreeCheckMod.HALF): TreeNode[] => {
            if (this.checkRelation === "related") {
                if (mode === TreeCheckMod.FULL) {
                    return this.getFullChecked();
                }
                if (mode === TreeCheckMod.CHILD) {
                    return this.getChildChecked();
                }
                if (mode === TreeCheckMod.HALF) {
                    return this.getHalfChecked();
                }
                if (mode === TreeCheckMod.SHALLOW) {
                    return this.getShallowChecked();
                }
            } else {
                return this.getFullChecked();
            }
            return [];
        };

        /**
         * 获取所有选中的节点包含父节点和子节点
         * @returns
         */
        getFullChecked = () => {
            return this.flatData.filter((node: TreeNode) => node.checked === true);
        };

        /**
         * 选中的子节点
         * @returns
         */
        getChildChecked = () => {
            return this.flatData.filter((node: TreeNode) => node.checked === true && (!node.children || node.children.length === 0));
        };

        /**
         * 返回全部选中子节点和部分选中的父节点
         * @returns
         */
        getHalfChecked = () => {
            return this.flatData.filter((node: TreeNode) => this.store.nodeMap[node[this.keyField]].checked === true || this.store.nodeMap[node[this.keyField]].checked === "indeterminate");
        };

        /**
         * 如果父节点下所有子节点全部选中，只返回父节点
         * @returns
         */
        getShallowChecked = () => {
            const ret: TreeNode[] = [];
            this.flatData.forEach((node: TreeNode) => {
                if (node.checked === true) {
                    const parentChecked = (() => {
                        const parent = node._parent;
                        if (!parent) {
                            return false;
                        }
                        return parent.checked === true;
                    })();
                    if (!parentChecked) {
                        ret.push(node);
                    }
                }
            });
            return ret;
        };

        /**
         * 选中的节点标识
         * @param mode
         * @returns
         */
        getCheckedKeys = (mode: TreeCheckMod = TreeCheckMod.HALF): NodeKeyType[] => {
            const nodes = this.getChecked(mode);
            return nodes.map((node: TreeNode) => node[this.keyField]);
        };

        setNodeDragging = (nodeId: NodeKeyType, dragging: boolean) => {
            const node = this._getNode(nodeId);
            if (node) {
                node._dragging = dragging;
            }
        };

        clearChecked = () => {
            this.flatData.forEach((node: TreeNode) => {
                node.checked = false;
            });
        };

        setCheckedByMod = (val: NodeKeyType[]) => {
            this.clearChecked();
            if (this.checkRelation === "related") {
                if (this.mode === TreeCheckMod.FULL) {
                    this.setCheckedByFull(val);
                }
                if (this.mode === TreeCheckMod.HALF) {
                    this.setCheckedByHalf(val);
                }
                if (this.mode === TreeCheckMod.CHILD) {
                    this.setCheckedByChild(val);
                }
                if (this.mode === TreeCheckMod.SHALLOW) {
                    this.setCheckedByShallow(val);
                }
            } else {
                this.setCheckedByFull(val);
            }
        };

        setCheckedByFull = (val: NodeKeyType[]) => {
            val.forEach((key: NodeKeyType) => {
                this.store.nodeMap[key].checked = true;
                this.checkRelation === "related" && this.setCheckedForwardUp(this.store.nodeMap[key]);
            });
        };

        setCheckedByHalf = (val: NodeKeyType[]) => {
            val.forEach((key: NodeKeyType) => {
                const node = this._getNode(key);
                if (!node.children || node.children.length === 0) {
                    this.store.nodeMap[key].checked = true;
                    this.checkRelation === "related" && this.setCheckedForwardUp(this.store.nodeMap[key]);
                }
            });
        };

        setCheckedByChild = (val: NodeKeyType[]) => {
            val.forEach((key: NodeKeyType) => {
                const node = this._getNode(key);
                if (!node.children || node.children.length === 0) {
                    this.store.nodeMap[key].checked = true;
                    this.checkRelation === "related" && this.setCheckedForwardUp(this.store.nodeMap[key]);
                }
            });
        };

        setCheckedByShallow = (val: NodeKeyType[]) => {
            val.forEach((key: NodeKeyType) => {
                const node = this.store.nodeMap[key];
                node.checked = true;
                this.checkRelation === "related" && this.setCheckedForwardUp(node);
                this.checkRelation === "related" && this.setCheckedForwardDown(node, true);
            });
        };
    }
</script>
